package edu.scsu.csci.util;


import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import edu.scsu.csci.core.ApplicationConfiguration;
import edu.scsu.csci.core.DOField;
import edu.scsu.csci.core.FrameWorkUncheckedException;

/**
 * Wrapper class to deal with some reflection.
 *  
 * @author Maqbool khan
 */
public class ReflectionUtil {
	
	/**
	 * <p>	Returns the getter method for the given field name.
	 * 
	 * @param type
	 * @param property
	 * @return
	 * @throws MethodInvocationException
	 */
	public static Method getAccessor(Class clazz, String property)	{
		return getMethod(clazz, "get" + Character.toUpperCase(property.charAt(0)) + property.substring(1));
	}
	
	/**
	 * Retrieves the setter method in an object for the specified property
	 * 
	 * @param current          the current object
	 * @param property         the setter to find
	 * @return the setter or null if not found
	 */
	public static Method getMutator(Class clazz, String property) {
		return getMethod(clazz, "set" + Character.toUpperCase(property.charAt(0))+ property.substring(1));
		
	}
	
	/**
	 * @param type
	 * @return
	 * @throws InstantiationException
	 */
	public static Object genericInstantiate(Class<?> type)
	throws InstantiationException {
		if (type.isArray())
			return Array.newInstance(type.getComponentType(), 0);
		return instantiate(type);
	}
	
	
	/**
	 * <p>	Returns a class object represented by the <code>className</code> name passed.
	 * 
	 * @param className
	 * @return
	 * @throws ClassLoadException
	 */
	public static Class getClass(String className) throws ClassLoadException{
		try {
			return Class.forName(className);
		}	catch (ClassNotFoundException e) {
			throw new ClassLoadException("Class could not be found.", e);			
		}
	}
	
	
	/**
	 * Returns a list of DOField objects for the given domain object.
	 * 
	 * @param DO
	 * @return
	 */
	public static DOField getDOField(Object DO, String fieldName){
		Field field;
		try {
			field = DO.getClass().getDeclaredField(fieldName);
			return new DOField(field, DO);
		}
		catch (SecurityException e) {
			throw new InvocationException(e.getLocalizedMessage());
		}
		catch (NoSuchFieldException e) {
			throw new InvocationException(e.getLocalizedMessage());}
	}
	
	/**
	 * Returns a list of DOField objects for the given domain object.
	 * 
	 * @param DO
	 * @return
	 */
	public static List<DOField>	getDOFields(Object DO){
		List<DOField> fieldList = new ArrayList<DOField>();
		Field[] fields = DO.getClass().getDeclaredFields();
		for(Field f : fields){
			fieldList.add( new DOField(f, DO));
		}
		return fieldList;
	}
	
	/**
	 * <p>	Returns the value for the field belonging to this object.
	 * 
	 * @param DO
	 * @param field
	 * @return
	 * @throws AccessorException
	 */
	public static Object getFieldValue(Object DO, Field field)	throws AccessorException {
		try {
			return field.get(DO);
		} catch (IllegalAccessException e) {
			ApplicationConfiguration.getInstance(null).getLogger().error(e, "Unable to get field ", field.getName(), ":",  e.getMessage());
			throw new AccessorException("Unable to get field " + field.getName()
						+ ": " + e.getMessage(), e);
		}
	}
	
	/**
	 * Returns a method.
	 * 
	 * @param clazz      the clazz where we are looking for a method
	 * @param name       the method name
	 * @return 			 the found method
	 */
	public static Method getMethod(Class<?> clazz, String methodName)	{
		Method[] methods = clazz.getMethods();
		for(Method m: methods){
			if(m.getName().equalsIgnoreCase(methodName)){
				return m;
			}
		}
		return null;
	}
	
	/**
	 * Returns a method.
	 * 
	 * @param clazz      the clazz where we are looking for a method
	 * @param name       the method name
	 * @return 			 the found method
	 */
	public static Method getMethod(Class<?> clazz,String methodName, Class parameterType)	{
		try {
			return clazz.getMethod(methodName, parameterType);
		} catch (NoSuchMethodException e) {
			return null;
		}
	}
	
	/**
	 * <p>	Instantiates an object of the given <code>clazz</code>.
	 * 
	 * @param <T>
	 * @param clazz
	 * @return
	 * @throws InstantiationException
	 */
	public static <T> T instantiate(Class<T> clazz)	throws InstantiationException {
		try {
			return clazz.newInstance();
		} catch (Exception e) {
			ApplicationConfiguration.getInstance(null).getLogger().error(e, "Unable to instantiate class",  clazz.getName());
			throw new InstantiationException(e.getMessage(), e);
		}
	}
	
	/**
	 * Tries to instantiate this class
	 * 
	 * @param class
	 *            the class to instantiate
	 * @return the instance
	 * @throws ComponentInstantiationException
	 */
	public static Object instantiate(String clazz)
	throws InstantiationException {
		try {
			return instantiate(Class.forName(clazz));
		} catch (ClassNotFoundException e) {
			throw new InstantiationException("Unable to instantiate "
						+ clazz, e);
		}
	}
	
	/**
	 * @param type
	 * @return
	 * @throws InstantiationException
	 */
	public static Object instantiateCollection(Type type)
			throws InstantiationException {
		Class clazz = (Class) type;
		if (List.class.isAssignableFrom(clazz))
			return new ArrayList<Type>();
		if (Set.class.isAssignableFrom(clazz))
			return new LinkedHashSet<Type>();
		throw new InstantiationException(
		"Unable to instantiate the desired collection");
	}
	
	
	
	/**
	 * <p>	Invokes the given method.
	 * 
	 * @param object
	 * @param method
	 * @param parameters
	 * @return
	 * @throws InvocationException
	 */
	public static Object invoke(Object object, Method method,
				Object... parameters) throws InvocationException {
		try {
			return method.invoke(object, parameters);
		} catch (InvocationTargetException e) {
			ApplicationConfiguration.getInstance(null).getLogger().error(e, e.getMessage());
			throw new InstantiationException(e.getMessage(), e.getCause());
		} catch (Exception e) {
			ApplicationConfiguration.getInstance(null).getLogger().error(e, "Unable to execute", method.getName(), ":"
						,e.getMessage());
			throw new InvocationException("Unable to ivoke "
						+ method.getName() + ": " + e.getMessage(), e);
		}
	}
	
	/**
	 * <p>	Invokes the given method name.
	 * 
	 * @param object
	 * @param method
	 * @param parameters
	 * @return
	 * @throws InvocationException
	 */
	public static Object invoke(Object object, String methodName,
				Object parameter) throws InstantiationException {
		try {
			return getMethod(object.getClass(), methodName, parameter.getClass()).invoke(object, parameter);
		} catch (InvocationTargetException e) {
			ApplicationConfiguration.getInstance(null).getLogger().error(e, e.getMessage());
			throw new InstantiationException(e.getMessage(), e.getCause());
		} catch (Exception e) {
			ApplicationConfiguration.getInstance(null).getLogger().error(e, "Unable to execute", methodName, ":"
						,e.getMessage());
			throw new InvocationException("Unable to invoke "
						+ methodName + ": " + e.getMessage(), e);
		}
	}
	
	
	
	/**
	 * <p>	Sets a the given value to the fiels belonging to the given object.
	 * 
	 * @param DO           component
	 * @param field		          field
	 * @param value		          new value
	 * @throws SettingException   unable to call setter
	 */
	public static void setField(Object DO, Field field, Object value)
	throws MutatorException {
		try {
			field.set(DO, value);
		} catch (IllegalArgumentException e) {
			ApplicationConfiguration.getInstance(null).getLogger().error( e, "Unable to set field"
						, field.getName(),":", e.getMessage());
			throw new MutatorException("Unable to set field " + field.getName()
						+ ": " + e.getMessage(), e);
		} catch (IllegalAccessException e) {
			ApplicationConfiguration.getInstance(null).getLogger().error(e, "Unable to set field", field.getName() , ":" , e.getMessage());
			throw new MutatorException("Unable to set field " + field.getName()
						+ ": " + e.getMessage(), e);
		}
	}
	
	
	/**
	 * <p> indicates that a domain object field could not be retrieved.
	 * 
	 * @author Maqbool Khan
	 * @since jdk 1.5
	 * @see
	 */
	public static class AccessorException extends FrameWorkUncheckedException 
	{
		/**
	     * class serialization id.
	     */
	    private static final long serialVersionUID = 96242378935343447L;
		
		    
	    /**
	     * @param exceptionMessage
	     * @param e
	     */
	    public AccessorException(String exceptionMessage, Throwable e) {
			super(exceptionMessage, e);
		}


		/**
		 * @param exceptionMessage
		 */
		public AccessorException(String exceptionMessage) 
		{
			super(exceptionMessage);
		}
	}
	
	/**
	 * <p> indicates that a domain object field could not be set.
	 * 
	 * @author Maqbool Khan
	 * @since jdk 1.5
	 * @see
	 */
	public static  class MutatorException extends FrameWorkUncheckedException 
	{
		/**
	     * class serialization id.
	     */
	    private static final long serialVersionUID = 96242378935343447L;
			
	    
	    /**
	     * @param exceptionMessage
	     * @param e
	     */
	    public MutatorException(String exceptionMessage, Throwable e) {
			super(exceptionMessage, e);
		}


		/**
		 * @param exceptionMessage
		 */
		public MutatorException(String exceptionMessage) 
		{
			super(exceptionMessage);
		}
	}
	
	/**
	 *
	 * @author Maqbool Khan
	 * @since jdk 1.5
	 * @see
	 */
	public static class InstantiationException extends FrameWorkUncheckedException 
	{
		/**
	     * class serialization id.
	     */
	    private static final long serialVersionUID = 962423789353476147L;
			
	    
	    /**
	     * @param exceptionMessage
	     * @param e
	     */
	    public InstantiationException(String exceptionMessage, Throwable e) {
			super(exceptionMessage, e);
		}


		/**
		 * @param exceptionMessage
		 */
		public InstantiationException(String exceptionMessage)	{
			super(exceptionMessage);
		}

	}
	
	/**
	 * <p> Indicates a class load exception.
	 * 
	 * @author Maqbool Khan
	 * @since jdk 1.5
	 * @see
	 */
	public static class ClassLoadException extends FrameWorkUncheckedException	{
		
		/**
	     * class serialization id.
	     */
	    private static final long serialVersionUID = 962423232565343447L;
		
		
	    
	    /**
	     * @param exceptionMessage
	     * @param e
	     */
	    public ClassLoadException(String exceptionMessage, Throwable e) {
			super(exceptionMessage, e);
		}


		/**
		 * @param exceptionMessage
		 */
		public ClassLoadException(String exceptionMessage)	{
			super(exceptionMessage);
		}
	}

	/**
	 *  
	 * @author Maqbool Khan
	 * @since jdk 1.5
	 * @see
	 */
	public static class InvocationException extends FrameWorkUncheckedException	{
		
		/**
	     * class serialization id.
	     */
	    private static final long serialVersionUID = 9624232322324447L;
		
		
	    
	    /**
	     * @param exceptionMessage
	     * @param e
	     */
	    public InvocationException(String exceptionMessage, Throwable e) {
			super(exceptionMessage, e);
		}


		/**
		 * @param exceptionMessage
		 */
		public InvocationException(String exceptionMessage)	{
			super(exceptionMessage);
		}
	}
}
